'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
const clone_1 = require("./clone");
const Proxy_1 = require("./Proxy");
class Timeline {
    constructor(maxSnapshots = 10) {
        this.startTime = null;
        this.lastSnapshotTime = -1;
        this.history = [];
        this.maxSnapshots = maxSnapshots;
    }
    takeSnapshot(state, elapsedTime) {
        if (!this.startTime) {
            this.startTime = Date.now();
        }
        const now = (!elapsedTime) ? Date.now() : this.startTime + elapsedTime;
        const end = now - this.startTime;
        //
        // history timeframe:
        //
        // { "start": 0, "end": 1000, "data": {} }
        //
        this.history.push({
            data: clone_1.clone(state),
            end,
            start: this.lastSnapshotTime,
        });
        // drop older history
        if (this.history.length > this.maxSnapshots) {
            this.history.shift();
        }
        this.lastSnapshotTime = end;
    }
    /**
     * Get snapshot taken at `elapsedTime` interval.
     */
    at(elapsedTimeAt, interpolate = true) {
        let i = this.history.length;
        let lesserDistance = Infinity;
        let lesserDistanceIndex = 0;
        while (i--) {
            const frame = this.history[i];
            const duration = ((frame.end - frame.start) / 2) - 1; // -1 is to prevent equal comparations
            const startDistance = Math.sqrt(Math.pow(elapsedTimeAt - frame.end - duration, 2));
            const endDistance = Math.sqrt(Math.pow(elapsedTimeAt - frame.end + duration, 2));
            if (startDistance < lesserDistance || endDistance < lesserDistance || elapsedTimeAt === frame.end) {
                lesserDistance = Math.min(startDistance, endDistance);
                lesserDistanceIndex = i;
            }
        }
        const frame = this.history[lesserDistanceIndex];
        const data = frame.data;
        //
        // traverse all properties to interpolate / extrapolate numbers
        //
        if (interpolate && elapsedTimeAt !== frame.end) {
            const previousState = this.at(frame.start, false);
            let multiplier = 1;
            if (elapsedTimeAt > frame.end) {
                //
                // extrapolation multiplier
                //
                // TODO: a better extrapolation method is needed.
                // needs to consider all previous states to evaluate a consistent next possible value
                //
                multiplier = -(elapsedTimeAt - frame.end) / (frame.end - frame.start);
            }
            else {
                multiplier = (elapsedTimeAt - frame.start) / (frame.end - frame.start);
            }
            return Proxy_1.createProxy(data, previousState, multiplier);
            //
            // It may be a good idea to traverse everything at once instead of
            // lazy-evaluating through proxies
            //
            // this.traverseInterpolate( data, // previousState, multiplier )
            //
        }
        return data;
    }
    offset(offset, interpolate = true) {
        return this.at(this.lastSnapshotTime + offset, interpolate);
    }
}
exports.default = Timeline;
